package com.alex.common.persistent;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Repository;

import com.alex.common.ILogger;
import com.alex.common.cache.CommonCacheManager;
import com.alex.common.cache.ICacheable;
import com.alex.common.exception.AlexCommException;
import com.alex.common.tools.DateUtils;

@Repository
public class CommonDao extends BaseDao {

	private CommonDao() {
	}

	private static final CommonDao me = new CommonDao();

	public static CommonDao getInstance() {
		return me;
	}

	// public static final String PO = "pobj";

	private CommonCacheManager cacheMgr = CommonCacheManager.getInstance();

	/**
	 * no cached
	 * 
	 * @param pClaz
	 * @param conditions
	 *            e.g. "where something=?1"
	 * @param parameters
	 * @param first
	 * @param max
	 * @return
	 */
	public <T> List<T> query(final Class<T> pClaz, final String conditions,
			final List<?> parameters, final int first, final int max) {
		return (List<T>) dao(new Run<List<T>>() {

			@Override
			protected List<T> run(EntityManager em) {
				StringBuilder sql = new StringBuilder("from ").append(pClaz
						.getName());
				// add conditions
				if (conditions != null) {
					sql.append(" ").append(conditions);
				}
				Query query = em.createQuery(sql.toString());
				// add parameters
				if (parameters != null && parameters.size() > 0) {
					int i = 0;
					for (Object param : parameters) {
						query.setParameter(++i, param);
					}
				}
				if (max > 0) {
					query.setFirstResult(first);
					query.setMaxResults(max);
				}
				@SuppressWarnings("unchecked")
				List<T> list = query.getResultList();
				list.size();
				return list;
			}

		}, true);
	}

	/**
	 * cache supported
	 * 
	 * @param pClaz
	 * @return
	 */
	public Collection<?> query(final Class<? extends ICacheable> pClaz) {
		return (Collection<?>) dao(new Run<Collection<?>>() {

			@Override
			protected Collection<?> run(EntityManager em) {
				// find from cache
				String poKey = pClaz.getName();
				@SuppressWarnings("unchecked")
				Map<String, Object> poMap = (Map<String, Object>) cacheMgr.get(poKey);
				if (poMap != null) {
					ILogger.logger_common.debug(String.format(
							"Cache hit query: %s, size(%d)", poKey,
							poMap.size()));
					return poMap.values();
				}

				StringBuilder sql = new StringBuilder("from ").append(pClaz
						.getName());
				Query query = em.createQuery(sql.toString());
				Collection<?> list = (Collection<?>) query.getResultList();
				int size = list.size();

				// add to cache
				poMap = new HashMap<String, Object>(size);
				for (Object obj : list) {
					poMap.put(((ICacheable) obj).getBiz_key(), obj);
				}
				cacheMgr.put(poKey, (Serializable) poMap);

				return list;
			}
		}, true);
	}

	/**
	 * cache supported
	 * 
	 * @param dto
	 */
	public void insert(final Object dto) {
		dao(new Run<Boolean>() {

			@Override
			protected Boolean run(EntityManager em) {
				em.persist(dto);
				if (ICacheable.class.isInstance(dto)) {
					// add to cache
					String poKey = dto.getClass().getName();
					@SuppressWarnings("unchecked")
					Map<String, Object> poMap = (Map<String, Object>) cacheMgr.get(poKey);
					if (poMap == null) {
						poMap = new HashMap<String, Object>();
					}
					poMap.put(((ICacheable) dto).getBiz_key(), dto);
					cacheMgr.put(poKey, (Serializable) poMap);
				}
				return true;
			}
		}, false);
	}

	/**
	 * cache supported
	 * 
	 * @param pClaz
	 * @param id
	 */
	public void delete(final Class<?> pClaz, final Long id) {
		dao(new Run<Boolean>() {

			@Override
			protected Boolean run(EntityManager em) {
				Object po = em.find(pClaz, id);
				em.remove(po);
				if (ICacheable.class.isInstance(po)) {
					// delete from cache
					String poKey = po.getClass().getName();
					@SuppressWarnings("unchecked")
					Map<String, Object> poMap = (Map<String, Object>) cacheMgr.get(poKey);
					if (poMap != null) {
						poMap.remove(((ICacheable) po).getBiz_key());
						cacheMgr.put(poKey, (Serializable) poMap);
					}
				}
				return true;
			}
		}, false);
	}

	/**
	 * no cached
	 * 
	 * @param pClaz
	 * @param id
	 * @return
	 */
	public Object find(final Class<?> pClaz, final Long id) {
		return dao(new Run<Object>() {

			@Override
			protected Object run(EntityManager em) {
				return em.find(pClaz, id);
			}
		}, true);
	}

	/**
	 * cache supported
	 * 
	 * @param pClaz
	 * @param biz_key
	 * @return
	 */
	public Object find(final Class<? extends ICacheable> pClaz,
			final String biz_key) {
		return dao(new Run<Object>() {

			@Override
			protected Object run(EntityManager em) {
				// find from cache
				String poKey = pClaz.getName();
				@SuppressWarnings("unchecked")
				Map<String, Object> poMap = (Map<String, Object>) cacheMgr.get(poKey);
				if (poMap != null) {
					Object rt = poMap.get(biz_key);
					if (rt != null) {
						ILogger.logger_common.debug(String.format(
								"Cache hit find: %s, %s", poKey, biz_key));
						return rt;
					}
				}

				StringBuilder sql = new StringBuilder("from ").append(
						pClaz.getName()).append(" pobj where pobj.biz_key=?1");
				Query query = em.createQuery(sql.toString());
				query.setParameter(1, biz_key);
				Object po = query.getSingleResult();

				// add to cache
				if (poMap == null) {
					poMap = new HashMap<String, Object>();
				}
				poMap.put(((ICacheable) po).getBiz_key(), po);
				cacheMgr.put(poKey, (Serializable) poMap);

				return po;
			}
		}, true);
	}

	/**
	 * cache supported
	 * 
	 * @param valueMap
	 * @param clazName
	 * @param id
	 * @throws ClassNotFoundException
	 */
	public void update(final Map<String, Object> valueMap,
			final String clazName, final Long id) throws ClassNotFoundException {
		dao(new Run<Boolean>() {

			@Override
			protected Boolean run(EntityManager em) {
				Class<?> pClaz = null;
				try {
					pClaz = Class.forName(clazName);
				} catch (ClassNotFoundException e) {
					throw new AlexCommException(e);
				}
				Object po = em.find(pClaz, id);

				BeanWrapper bean = new BeanWrapperImpl(po);
				bean.registerCustomEditor(Date.class, new CustomDateEditor(
						new SimpleDateFormat(DateUtils.DEFAULT_FORMAT), true));
				bean.setPropertyValues(valueMap);
				Object dto = bean.getWrappedInstance();

				em.merge(dto);

				if (ICacheable.class.isInstance(dto)) {
					// add to cache
					String poKey = dto.getClass().getName();
					@SuppressWarnings("unchecked")
					Map<String, Object> poMap = (Map<String, Object>) cacheMgr.get(poKey);
					if (poMap == null) {
						poMap = new HashMap<String, Object>();
					}
					poMap.put(((ICacheable) dto).getBiz_key(), dto);
					cacheMgr.put(poKey, (Serializable) poMap);
				}
				return true;
			}
		}, false);
	}
}
